import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.text.DefaultCaret;

/*
 * TCSS 422 Process Control Simulator
 * Group Money: Al McKenzie, Michael Pitts, Taylor Zielske
 */

/**
 * Runs the simulation.
 * 
 * @author Al McKenzie
 * @author Michael Pitts
 * @version Dec 8, 2011
 */
public class Main {
	
	/** The default number of cycles a new Process will run. */
	public static final int DEFAULT_LIMIT = 15;
	
	/** Normal wait time for a single CPU cycle. */
	public static long DEFAULT_WAIT_TIME = 500;

	/** How many cycles between Timer interrupts. */
	private static final int DEFAULT_TIMER_WAIT = 15;
	
	/** If true, IO is not buffered. */
	private static final boolean UNBUFFERED = false;

	/** Indicates no IO for a particular process. */
	public static final int NO_IO = 0;

	/** Indicates a small amount of IO for a process. */
	public static final int SOME_IO = 1;

	/** Indicates a large amount of IO for a process. */
	public static final int MUCH_IO = 2;

	/** Indicates the Process will produce Shared data. */
	public static final int PRODUCER = 3;

	/** Indicates the Process will consume Shared data. */
	public static final int CONSUMER = 4;
	
	/** The standard printStream for the simulation. */
	private static PrintStream my_out;
	
	/** The Available IODevices for the CPU. */
	private final IODevice[] my_devices;

	/** The Scheduler that will be used for the simulation. */
	private final Scheduler my_scheduler;

	/** The CPU that will be used for the simulation. */
	private final CPU my_cpu;

	/** The Shared data. */
	private final Shared my_shared_data;
	
	/** The text area output is written to. */
	private final JTextArea my_text_area;
	
	/** The args passed at the command line. */
	private String[] my_args;

	/**
	 * Starts the simulation.
	 * 
	 * @param the_args
	 *            are the command line arugments feed in at run time.
	 */
	public static void main(String[] the_args) {
		new Main(the_args).start();
	}
	
	/**
	 * Creates a new simulation ready to run.
	 * @param the_args are the command line arguments.
	 */
	public Main(final String[] the_args) {
		my_text_area = new JTextArea();
		my_out = new TextOutStream(my_text_area).getStream();
		my_args = the_args;
		my_scheduler = new BasicScheduler();
		my_cpu = new CPU(my_scheduler, DEFAULT_TIMER_WAIT, UNBUFFERED);
		my_shared_data = new Shared(my_scheduler);
		my_devices = new IODevice[] {new IODevice("Hard Disk", my_cpu),
				new IODevice("Network", my_cpu),
				new IODevice("Expansion Card", my_cpu) };
	}
	
	/**
	 * Gets the printStream for this simulation.
	 * @return the standard PrintStream for this project.
	 */
	public static PrintStream out() {
		return my_out;
	}
	
	/**
	 * Starts the simulation.
	 */
	public void start() {
		System.out.println("Welcome to the Scheduler Simulation v1.0\n");
		// printTeamName();
		if (my_args.length < 2) {
			my_args = promptForSettings();
		}
		run(my_args);
	}

//	private static void printTeamName() {
//
//		OUT.println("		        d8888 88888888888 888b     d888 ");
//		OUT.println("		       d88888     888     8888b   d8888 ");
//		OUT.println("		      d88P888     888     88888b.d88888 ");
//		OUT.println("		     d88P 888     888     888Y88888P888 ");
//		OUT.println("		    d88P  888     888     888 Y888P 888 ");
//		OUT.println("		   d88P   888     888     888  Y8P  888 ");
//		OUT.println("		  d8888888888     888     888   Y   888 ");
//		OUT.println("		 d88P     888     888     888       888 ");
//		OUT.println("");
//	}

	/**
	 * Runs the simulation with the given, properly formatted arguments.
	 * 
	 * @param the_args
	 *            indicate which Process to load and maximum running time.
	 */
	private void run(final String[] the_args) {
		final Process[] processes = new Process[the_args.length - 1];
		for (int i = 0; i < the_args.length - 1; i++) {
			processes[i] = getProcess(Integer.parseInt(the_args[i]));
		}
		my_cpu.loadProcesses(processes);
		final long max = Long.parseLong(the_args[the_args.length - 1]);
		run(max);
	}

	/**
	 * Runs the CPU so that it's programs execute.
	 * 
	 * @param the_max
	 *            the maximum number of iterations to run.
	 */
	private void run(long the_max) {
		getFrame();
		while (the_max > 0) {
			my_cpu.runOneCycle();
			the_max--;
			try {
				Thread.sleep(DEFAULT_WAIT_TIME);
			} catch (InterruptedException e) {
				// sleep failure, would output quickly for one step
			}
		}
		my_out.println("DONE!  All required itterations run.");
		System.out.println("DONE!  All required iterations run.");
	}

	/**
	 * Prompts the user for input to determine which processes to run and how
	 * many iterations to run the simulation for.
	 * 
	 * @return a String array with the desired processes and mas iterations.
	 */
	private String[] promptForSettings() {
		final List<String> list = new ArrayList<String>();
		String input = null;
		BufferedReader is = new BufferedReader(new InputStreamReader(System.in));
		int val = 0;
		while (val > -1) {
			System.out.print("Enter Process: ");
			try {
				input = is.readLine();
				val = Integer.parseInt(input);
				list.add(input);
			} catch (Exception e) {
				val = -1; // exit loop
			}
			// OUT.println("I read this number: " + val);
		}
		input = null;
		while (input == null) {
			System.out.print("Maximum number of iterations?: ");
			try {
				input = is.readLine();
				Integer.parseInt(input);
				list.add(input);
			} catch (Exception e) {
				System.out.println("Unreadable number, please re-enter.");
				input = null;
			}
		}
		try {
			is.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return list.toArray(new String[list.size()]);
	}

	/**
	 * Gets a new Process based on the input given.
	 * 
	 * @param the_process
	 *            what new process to get.
	 * @return a new Process.
	 */
	private Process getProcess(final int the_process) {
		Process get;
		switch (the_process) {
		case SOME_IO:
			get = new BasicProcess("HD User", DEFAULT_LIMIT,
					new IODevice[] { my_devices[0] });
			break;
		case MUCH_IO:
			get = new BasicProcess("Lots of IO", DEFAULT_LIMIT, my_devices);
			break;
		case PRODUCER:
			get = new Producer("Producer", DEFAULT_LIMIT, my_shared_data);
			break;
		case CONSUMER:
			get = new Consumer("Consumer", DEFAULT_LIMIT, my_shared_data);
			break;
		default:
			get = new BasicProcess("Counter", DEFAULT_LIMIT, null);
			break;
		}
		return get;
		//create process array
		//CPU cpu = new CPU(processes, new BasicScheduler());
	}
	
	/**
	 * Starts a display Frame that catches keyboard input and displays all 
	 * simulation data as a console replication.
	 */
	private void getFrame() {
		JFrame frame = new JFrame("ATM Scheduler Simulator");
		KeyListener listener = new KeyAdapter() {
			@Override public void keyPressed(KeyEvent e) {
				if (e.getKeyCode() == KeyEvent.VK_SPACE) {
					my_cpu.addInterrupt(new Interrupt());
				} else {
					my_cpu.addInterrupt(new Interrupt("Keyboard Interrupt: Key '" + 
							e.getKeyChar() +"' was pressed"));
				}
			}
		};
		frame.addKeyListener(listener);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		my_text_area.setEditable(false);
		my_text_area.setFocusable(false);
		((DefaultCaret) my_text_area.getCaret()).
		setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
		final JScrollPane pane = new JScrollPane(my_text_area);
		pane.setFocusable(false);
		pane.setPreferredSize(new Dimension(600, 400));
		frame.add(pane, BorderLayout.CENTER);
		frame.setVisible(true);
		frame.setFocusable(true);
		frame.pack();
	}
}
